package fr.inserm.umr915.vcfserver.bio.berkeley;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;

import com.sleepycat.bind.tuple.IntegerBinding;
import com.sleepycat.bind.tuple.ShortBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.PreloadConfig;

import fr.inserm.umr915.vcfserver.bio.Chromosomes;
import fr.inserm.umr915.vcfserver.bio.Segment;

public class Storage
	{
	private static final Log LOG= LogFactory.getLog("fr.inserm.umr915");
	private BeanFactory factory;

 	public Environment env=null;
	/**
	 * KEY: long
	 * VALUE: short:db-id ; byte: data[]
	 */
	public Database id2object=null;
	public Database name2id=null;
	public Database loc2id=null;
	public Database dbid2longest=null;
	
	public long id_generator=0L;
	
	@SuppressWarnings("unchecked")
	private Map<Short, DatabaseManager<?>> id2dbManager=Collections.EMPTY_MAP;
	@SuppressWarnings("unchecked")
	private Map<String, DatabaseManager<?>> name2dbManager=Collections.EMPTY_MAP;
	@SuppressWarnings("unchecked")
	protected Map<Short, Integer> db2longest=Collections.EMPTY_MAP;
	@SuppressWarnings("unchecked")
	private List<DatabaseManager<?>> managers=Collections.EMPTY_LIST;
	
	private Chromosomes chromosomes=null;
	
	public Storage()
		{
		
		}
	
	public BeanFactory getFactory()
		{
		return factory;
		}
	
	public Chromosomes getChromosomes()
		{
		return chromosomes;
		}
	
	public void reloadMetaData()
		{
		LOG.info("reload metadata");
		/* find longest length */
		Map<Short,Integer> map=new HashMap<Short, Integer>();
		for(short dbId: id2dbManager.keySet())
			{
			map.put(dbId, 0);
			}
		
		if(this.dbid2longest!=null)
			{
			for(short dbId: this.id2dbManager.keySet())
				{
				DatabaseEntry key=new DatabaseEntry();
				DatabaseEntry data=new DatabaseEntry();
				ShortBinding.shortToEntry(dbId, key);
				if(this.dbid2longest.get(null,key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
					{
					int L=IntegerBinding.entryToInt(data);
					map.put(dbId,L);
					}
				else
					{
					map.put(dbId,100);
					}
				}
			}
		else
			{
			for(short dbId: id2dbManager.keySet())
				{
				map.put(dbId, 100);
				}
			}
		this.db2longest=Collections.unmodifiableMap(map);
		LOG.info("end reload metadata");
		}
	
	public synchronized void open(BeanFactory factory,boolean readOnly)
		throws IOException
		{
		close();
		boolean deferredWrite=false;
		this.chromosomes=(Chromosomes)factory.getBean("chromosomes");
		File dbHome=(File)factory.getBean("berkeley.db.home");
		if(dbHome==null) throw new RuntimeException("cannnot find bean @berkeley.db.home");
		if(!dbHome.exists()) throw new FileNotFoundException("\""+dbHome.toString()+"\"");
		if(!dbHome.isDirectory()) throw new FileNotFoundException("not a directory:"+dbHome.toString());
		this.factory=factory;
		EnvironmentConfig envCfg=new EnvironmentConfig();
		envCfg.setReadOnly(readOnly);
		envCfg.setAllowCreate(!readOnly);
		envCfg.setTransactional(false);
		envCfg.setConfigParam(EnvironmentConfig.LOG_FILE_MAX,"500000000");
		
		if(!readOnly)
			{
			envCfg.setCacheSize(10000000000L);//10Go
			LOG.info("cache:"+this.env.getConfig().getCacheSize());
			}
		this.env=new Environment(dbHome, envCfg);
		
		PreloadConfig preloadConfig=null;
		if(!readOnly)
			{
			preloadConfig=new PreloadConfig();
			}
		
		
		DatabaseConfig cfg=new DatabaseConfig();
		cfg.setReadOnly(readOnly);
		cfg.setAllowCreate(!readOnly);
		cfg.setSortedDuplicates(false);
		cfg.setDeferredWrite(deferredWrite);
		LOG.info("opend id2object");
		this.id2object=this.env.openDatabase(null, "id2object", cfg);
		if(preloadConfig!=null) this.id2object.preload(preloadConfig);
		
		cfg=new DatabaseConfig();
		cfg.setReadOnly(readOnly);
		cfg.setAllowCreate(!readOnly);
		cfg.setSortedDuplicates(true);
		cfg.setDeferredWrite(deferredWrite);
		LOG.info("opend name2id");
		this.name2id=this.env.openDatabase(null, "name2id", cfg);
		if(preloadConfig!=null) this.name2id.preload(preloadConfig);
		
		cfg=new DatabaseConfig();
		cfg.setReadOnly(readOnly);
		cfg.setAllowCreate(!readOnly);
		cfg.setSortedDuplicates(true);
		cfg.setDeferredWrite(deferredWrite);
		cfg.setBtreeComparator(Segment.Sorter.class);
		LOG.info("opend loc2id");
		this.loc2id=this.env.openDatabase(null, "loc2id", cfg);
		if(preloadConfig!=null) this.loc2id.preload(preloadConfig);
		
		
		if("TODO".isEmpty())//TODO
			{
			cfg=new DatabaseConfig();
			cfg.setReadOnly(readOnly);
			cfg.setAllowCreate(!readOnly);
			cfg.setDeferredWrite(deferredWrite);
			dbid2longest=this.env.openDatabase(null, "dbid2longest", cfg);
			if(preloadConfig!=null) this.dbid2longest.preload(preloadConfig);
			}
		
		
		
		@SuppressWarnings("unchecked")
		List<DatabaseManager<?>> list=(List<DatabaseManager<?>>)factory.getBean("database.managers");
		Map<Short,DatabaseManager<?>> map1=new HashMap<Short,DatabaseManager<?>>();
		Map<String,DatabaseManager<?>> map2=new TreeMap<String,DatabaseManager<?>>(String.CASE_INSENSITIVE_ORDER);
		for(DatabaseManager<?> mgr:list)
			{
			if(map1.containsKey(mgr.getId()) || map1.containsKey(mgr.getName()))
				{
				throw new IllegalArgumentException("duplicate db id:"+mgr);
				}
			map1.put(mgr.getId(), mgr);
			map2.put(mgr.getName(), mgr);
			}
		this.managers= Collections.synchronizedList(Collections.unmodifiableList(list));
		this.id2dbManager=Collections.synchronizedMap(Collections.unmodifiableMap(map1));
		this.name2dbManager=Collections.synchronizedMap(Collections.unmodifiableMap(map2));
		
		LOG.info("end open");
		}
	
	public List<DatabaseManager<?>> getDatabaseManagers()
		{
		return this.managers;
		}
	
	public Map<Short,DatabaseManager<?>> getIdToMap()
		{
		return this.id2dbManager;
		}
	
	public Map<String,DatabaseManager<?>> getNameToMap()
		{
		return this.name2dbManager;
		}
	
	public Integer getLongest(short dbId)
		{
		return this.db2longest.get(dbId);
		}
	
	@SuppressWarnings("unchecked")
	public synchronized void close()
		{
		
		this.id2dbManager=Collections.EMPTY_MAP;
		name2dbManager=Collections.EMPTY_MAP;
		db2longest=Collections.EMPTY_MAP;
		managers=Collections.EMPTY_LIST;
		
		
		if(dbid2longest!=null)
			{
			this.dbid2longest=null;
			dbid2longest.close();
			}
		
		if(this.name2id!=null)
			{
			this.name2id.close();
			this.name2id=null;
			}
		
		if(this.loc2id!=null)
			{
			this.loc2id.close();
			this.loc2id=null;
			}
		
		if(this.id2object!=null)
			{
			this.id2object.close();
			this.id2object=null;
			}
		if(this.env!=null)
			{
			if(!this.env.getConfig().getReadOnly())
				{
				LOG.info("clean & compress");
				try {this.env.cleanLog();}catch(Throwable err){}
				try {this.env.compress(); Thread.sleep(10*1000);}catch(Throwable err){}
				}
			try {this.env.close();}catch(Throwable err){}
			this.env=null;
			}
		this.factory=null;
		this.chromosomes=null;
		}
	}
